package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.Result;
import com.hmdp.dto.ScrollResult;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.Blog;
import com.hmdp.entity.Follow;
import com.hmdp.entity.User;
import com.hmdp.mapper.BlogMapper;
import com.hmdp.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IFollowService;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@Slf4j
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    UserServiceImpl userService;

    @Resource
    IFollowService followService;
    private static final String LIKE_KEY = "blog:like:";

    @Override
    public Result queryBlogById(Long id) {
        String userId = UserHolder.getUser().getId().toString();
        Blog blog = getById(id);
        Double score = stringRedisTemplate.opsForZSet().score(LIKE_KEY + id, userId);
        // 已点赞
        if (score != null) {
            blog.setIsLike(true);
        } else {
            blog.setIsLike(false);
        }

        return Result.ok(blog);
    }

    @Override
    public Result queryLikes(Long id) {
        Set<String> range = stringRedisTemplate.opsForZSet().range(LIKE_KEY + id, 0, 4);
        if(range == null || range.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        List<Long> ids = range.stream().map(Long::valueOf).collect(Collectors.toList());
        String idStr = StrUtil.join(",", ids);
        List<UserDTO> userDTOS = userService.query()
                // 查询对应userId的信息，需要按照in中id排列的顺序返回结果
                // 在mysql中，通过in查询，返回的数据会按照id升序返回，
                // 是我们需要按照传入id的顺序返回，所以需要通过ORDER BY FIELD(id, 3,2,1);处理 让mysql按照传入顺序返回
                .in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list()
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        log.info("range:{}" , range);
        return Result.ok(userDTOS);
    }

    @Override
    public Result updateLike(Long id) {
        Long userId = UserHolder.getUser().getId();
        // 判断当前用户是否已点赞
        Double score = stringRedisTemplate.opsForZSet().score(LIKE_KEY + id, userId.toString());
        // 已点赞
        if (score != null) {
            // 减少点赞数量
            update().setSql("liked = liked - 1").eq("id", id).update();
            // 移除点赞记录
            stringRedisTemplate.opsForZSet().remove(LIKE_KEY + id, userId.toString());
            return Result.ok();
        }

        // 未点赞 增加点赞数量
        update().setSql("liked = liked + 1").eq("id", id).update();
        // 添加当前用户点赞信息
        stringRedisTemplate.opsForZSet().add(LIKE_KEY + id, userId.toString(), System.currentTimeMillis());
        return Result.ok();
    }


    @Override
    public boolean isLike(Long id) {
        UserDTO user = UserHolder.getUser();
        // 用户未登录
        if (user == null) {
            return false;
        }
        // 有点赞记录则有分数（时间戳
        Double score = stringRedisTemplate.opsForZSet().score(LIKE_KEY + id, user.getId().toString());
        if (score != null) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Result saveBlog(Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        String userId = user.getId().toString();
        blog.setUserId(user.getId());
        // 保存探店博文
        boolean save = save(blog);
        if (!save) {
            return Result.fail("新增点评失败！");
        }
        // 查询粉丝
        List<Follow> fans = followService.query().eq("follow_user_id", userId).list();
        for (Follow follow : fans) {
            String key = "feed:" + follow.getUserId();
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        }

        // 返回id
        return Result.ok(blog.getId());
    }

    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        Long userId = UserHolder.getUser().getId();
        String key = "feed:" + userId;
        // 使用zset数据结构 对score倒序排序 取max-min之间的值 从第0条(偏移量)开始取 取2条
        // zrevrangebyscore key max min withscores limit 0 2
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        // 判断不为空
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();
        }
        // 解析需要返回给前端的数据 blogs minTime offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        // 时间戳最小值是0
        long minTIme = 0;
        // 从第二次查询开始，偏移量默认是1开始
        int ofs = 1;
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
            ids.add(Long.valueOf(tuple.getValue()));
            // 不断保存最小时间，这里是在不断从前往后遍历，遍历到最后一定是最小时间戳
            // 如果有相同的时间戳,那它们肯定是挨在一起的
            // 遇到有相同的时间戳说明偏移量需要+1了, 如果时间戳不同,说明当前时间戳与前一个时间戳不同,则重置时间戳和偏移量
            long time = tuple.getScore().longValue();
            if (time == minTIme) {
                ofs++;
            } else {
                minTIme = time;
                ofs = 1;
            }
        }
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

        for (Blog blog : blogs) {
            // 查询blog有关的用户
            queryBlogUser(blog);
            // 查询blog是否被点赞
            isBlogLiked(blog);
        }

        ScrollResult scrollResult = new ScrollResult();
        scrollResult.setList(blogs);
        scrollResult.setOffset(ofs);
        scrollResult.setMinTime(minTIme);

        return Result.ok(scrollResult);
    }

    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

    private void isBlogLiked(Blog blog) {
        // 1.获取登录用户
        UserDTO user = UserHolder.getUser();
        if (user == null) {
            // 用户未登录，无需查询是否点赞
            return;
        }
        Long userId = user.getId();
        // 2.判断当前登录用户是否已经点赞
        String key = "blog:liked:" + blog.getId();
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        blog.setIsLike(score != null);
    }
}
